use objc2::rc::Id;
use objc2::runtime::ProtocolObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};

use super::*;

declare_class!(
	pub struct AppDelegate;
	unsafe impl ClassType for AppDelegate {
		type Super = NSObject;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopAppDelegate";
	}
	impl DeclaredClass for AppDelegate {}
	unsafe impl NSObjectProtocol for AppDelegate {}
	unsafe impl NSApplicationDelegate for AppDelegate {
		#[method(applicationDidFinishLaunching:)]
		fn did_finish_launching(&self, _notification: &NSNotification) {
			self.did_finish_launching_(_notification);
		}
		#[method(applicationShouldTerminateAfterLastWindowClosed:)]
		fn application_should_terminate_after_last_window_closed(&self, _sender: &NSApplication) -> bool {
			true
		}
		#[method(applicationWillTerminate:)]
		fn application_will_terminate(&self, notification: &NSNotification) {
			self.application_will_terminate_(notification);
		}
	}
);

impl AppDelegate {
	pub fn build() -> Id<NSApplication> {
		let mtm = MainThreadMarker::new().unwrap();
		let app = NSApplication::sharedApplication(mtm);
		app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
		let this: Id<Self> = unsafe { msg_send_id![mtm.alloc(), init] };
		app.setDelegate(Some(ProtocolObject::from_ref(&*this)));
		app
	}
	fn did_finish_launching_(&self, notification: &NSNotification) {
		if let Some(app) = unsafe { notification.object() } {
			let app = from_anyobject::<NSApplication>(&app);
			CACHE.with_borrow(|vars| {
				if let Some(rep) = &vars.rep {
					rep.on_launch(AppRef(app));
				}
			});
			unsafe { app.activate() };
		}
	}
	fn application_will_terminate_(&self, notification: &NSNotification) {
		if let Some(app) = unsafe { notification.object() } {
			let app = from_anyobject::<NSApplication>(&app);
			CACHE.with_borrow(|vars| {
				if let Some(rep) = &vars.rep {
					rep.on_terminate(AppRef(app));
				}
			});
		}
	}
}
fn from_anyobject<T>(value: &objc2::runtime::AnyObject) -> &T {
	use objc2::runtime::AnyObject;
	use std::ffi::c_void;
	unsafe {
		let value = value as *const AnyObject;
		let value = value as *const c_void;
		let value = value as *const T;
		&*value
	}
}
